# $Id: cmdparser.py 70fe0286d122 2013/07/17 05:39:39 pgurumur $
# Copyright (c) 2009 Prabhu Gurumurthy  <pgurumur@gmail.com>
# 
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#

## package @cmdline
# Command line option parser class using optparse module, inspired from
# BaseCommand available from django project, uses some of the class logic
# but with few major changes

__version__  = "$Revision: 70fe0286d122 $"
__author__   = "$Author: pgurumur $"
__modified__ = "$Date: 2013-07-17 05:39:39Z $"

# Import optparse module
import optparse
import warnings

# Local imports
import stdlib
from exception import GetOptError

# Decorator to parse the options before calling the handle function,
# to be invoked by the caller
def getopts(function):
   """
   @brief getopts decorator to parse the command line options/arguments using
          optparse
   @param function 'function' to call within the wrapper
   @return returns wrapper function
   """
   def wrapper(*args, **kwargs):
      """
      @brief wrapper function that just using function parameter from parent
              function and the passes in args and kwargs
      @param args 'list of arguments'
      @param kwargs 'keyword arguments'
      @return 'void function'
      """
      instance = args[0]
      try:
         (options, arguments) = instance.parse()
         instance.options = options
         instance.arguments = arguments
      except:
         raise
      else:
         function(*args, **kwargs)

   return wrapper

## GetOpt Class, inherits standard python object class
class GetOpt(object):
   """
   1. This class takes functional parameters are part of the constructor
      The following keys are expected as parameters
      version = Version number, that will be converted to a floating value
                used when --version option is invoked.

      args = Entire sys.argv used to get option and positional parameter
             from the command line

      This is option parameter, this can be get up globally by declaring
      usage before __init__ function, just like help can be implemented
      usage = String that is used when positional parameter is needed along
              with command line options
         Example:
            without usage parameter,
            progname [ options ]

            when usage = "host" is given:
            progname [ options ] host

   2. There is a default option used -v | --verbosity, used for either
      debug or for the code to be more verbose, this can be overwritten
      when the inheriting class does not add to the option_list, but
      re-declares it, if builtin add_option is used, then -v option is
      inherited.

   3. handle(): Function that is the starting point for all the subclasses,
                all logic for the subclasses are placed in handle,
                Using the cmdparser.getopts decorator, even the parse command
                can be obscured from handle function or it may be used to
                parse the command line options manually and then call other
                functions as needed.

   4. Subclass may call the len() method on this class to see how
      many command line options have be passed to make decisions
      Example:
         if len(cmd) <= 1:
            print cmd.printhelp()
         else:
            cmd.handle()

         where cmd is the instantiation of GetOpt subclass

   5. add_option function is similar to optparse.make_option, in fact it calls
      optparse.make_option, it is a useful function, which helps the inheriting
      class not to call optparse as a required module, all the inheriting class
      needs to do is just to call cmdparser class. This is a utility function

   6. help variable defined before init function is an optional variable that
      can be set to provide helpful examples, caveats or other usage related
      information for using the script and helping the user invoking it
   """

   ###
   """
   Metadata for GetOpt class, these are the list of options that this
   class is going to look for in the command line. The default options
   for optparse are --version and -h |-help
   """

   option_list = (
         optparse.make_option(
            "-v", "--verbosity", action = "store", dest = "verbosity",
            default = 0, type = "choice", choices = ["0", "1", "2"],
            help = "Verbosity level; 0 = minimal, 1 = normal 2 = all"),
   )

   # Help string, declared here to be used by the inheriting class to print
   # help
   help = None

   # Args, typically from sys.argv
   args = None

   # Usage string to be used
   usage = None

   # options that would added typically by the decorator
   options = None
   arguments = None

   ## GetOpt constructor
   def __init__(self, **Keywords):
      """
      @brief Builds the GetOpt class with an optparse parser
      @param self 'this' pointer for the class
      @param Keywords functional parameter
      @return return GetOpt object
      @author $Author: pgurumur $
      @version $Revision: 70fe0286d122 $

      """

      # Instantite the error class
      # self._err = GetOptError()

      # Declare the variables used in this class
      self._version = 0.0
      self._progname = None
      self._usage = None
      self._parser = None
      self._argc = 0

      if self.help:
         self.__doc__ = self.help

      # Arguments parameters, typicaly sys.argv passed
      if "args" in Keywords:
         self._argc = len(Keywords["args"])
         self.args = Keywords["args"]
      else:
         if self.args is None:
            raise GetOptError("no arguments given")
         else:
            self._argc = len(self.args)

      # Constuct the progname, similar to basename command, remove extraneous
      # forward slashes
      self._progname = self.args[0].split("/")[-1]

      # Usage parameter is used to specify whether positional parameters will
      # be used apart from the command line options parsed by this class
      if "usage" in Keywords:
         usage = Keywords["usage"]
         self._usage = "%s [ options ] %s" %(str(self), usage)
      else:
         if self.usage:
            self._usage = "%s %s" %(str(self), self.usage)
         else:
            self._usage = "%s [ options ]" %str(self)

      # If the version keyword is present, then try converting the value
      # hopefully an integer or float into a floating value. Any other value 
      # will result in a ValueError exception being raised, which in turn
      # is converted into GetOptError
      if "version" in Keywords:
         try:
            self._version = float(Keywords["version"])
         except ValueError as err:
            self.error(err.message)

      # If help string is present, used along with usage
      # Help string may be used to briefly describe what the inheriting class
      # is being used for
      if self.help:
         self._usage += "\n%s" %self.help

      # Try and construct the optparser, errors if any GetOptError is raised
      # by this class
      try:
         self._parser = optparse.OptionParser(prog = str(self),
               usage = self._usage,
               version = "%s version %0.01f" %(str(self), self._version),
               option_list = self.option_list)
      except optparse.OptionError as err:
         self.error(err.message)

   @property
   def version(self):
      """
      @brief: Return the version used by the inheriting class
      @parem self 'this' pointer
      @return version value as a float
      """
      return self._version

   def parse(self):
      """
      @brief: Parse the command line, which would be passed as args to this
              class
      @parem self 'this' pointer
      @return A tuple containing options, command line options and args
             positional parameter
      """
      return self._parser.parse_args(self.args)

   def printhelp(self):
      """
      @brief: Prints help, if a valid parser has been constructed by init
      @parem self 'this' pointer
      @return a void function.
      """
      if self._parser:
         self._parser.print_help()

   def cout(self, fmt, *args):
      """
      @brief: function used to print to stdout
      @param: self 'this' pointer, fmt and *args are similar to printf in C"
      @return: Nothing, just prints!"
      """
      stdlib.printf(fmt, *args)

   def warning(self, fmt, *args):
      """
      @brief: function used to print warnings
      @param: self 'this' pointer, fmt and *args are similar to printf in C"
      @return: Nothing, just prints!"
      """
      stdlib.colorprint("red", fmt, *args)

   def error(self, fmt, *args):
      """
      @brief: function used to raise errors
      @parem self 'this' pointer
      @return raises GetOptError, with the message
      """
      errmesg = None
      if len(args):
         errmesg = stdlib.sprintf(fmt, *args)
      else:
         errmesg = stdlib.my_str(fmt)

      if hasattr(self, 'close'):
         self.close()

      raise GetOptError(errmesg, str(self))

   def onelinewarn(self, message, category, filename, lineno, file = None,
         line = None):
      return "%s(warning) line %s: %s\n" %(str(self), lineno, message)

   def warn(self, fmt, *args):
      errmesg = None
      if len(args):
         errmesg = stdlib.sprintf(fmt, *args)
      else:
         errmesg = stdlib.my_str(fmt)

      warnings.formatwarning = self.onelinewarn
      warnings.warn(errmesg)

   def handle(self):
      """
      @brief: Function that is implemented by the subclasses
      @param self 'this' pointer
      """
      raise NotImplementedError()

   def __len__(self):
      """
      @brief: returns the number of command line options passed
      @param self 'this' pointer
      """
      return self._argc

   def __str__(self):
      """
      @brief: returns the program name as the str for this class
      @param self 'this' pointer
      """
      return stdlib.my_str(self._progname)

   def __repr__(self):
      """
      @brief: calls str() function
      @param self 'this' pointer
      """
      return repr(str(self))

   def __unicode__(self):
      """
      @brief: calls str() function
      @param self 'this' pointer
      """
      return unicode(str(self))

   def add_option(self, *Args, **Keywords):
      """
      @brief: Function that builds the option list, in this case, instead
              of building the option list before the init, build the option
              durint init, the only caveat being, options must be added in
              the child class before parent is called, (i.e. before super is
              called), this function adds to the existing option_list, if
              that is not needed, the old way of instantiating the option_list
              before init is still supported
      @param self 'this' pointer
      @param Args list of arguments
      @param Keywords key/value pair arguments
      """
      # The option list needs to be a tuple
      self.option_list += (optparse.make_option(*Args, **Keywords),)

   def __setitem__(self, *Args, **Kwargs):
      self.add_option(*Args, **Kwargs)

   def __getitem__(self, key):
      retval = None
      if hasattr(self.options, key):
         retval = getattr(self.options, key)

      return retval

   length = property(fget = lambda self: len(self))
